package com.hkts.beltweigher.opc;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

/**
 * @ClassNameClientExampleRunner
 * @Description
 * @Author yck
 * @Date28/7/2022 上午9:11
 * @Version V1.0
 **/
@Slf4j
public class ClientExampleRunner {
    private  String tEndpointUrl;
    static {
        // Required for SecurityPolicy.Aes256_Sha256_RsaPss
        Security.addProvider(new BouncyCastleProvider());
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();

   // private ExampleServer exampleServer;

    private DefaultTrustListManager trustListManager;

    private final ClientExample clientExample;
    private final boolean serverRequired;

    public ClientExampleRunner(ClientExample clientExample,String tEndpointUrl) throws Exception {
        this(clientExample, true);
        this.tEndpointUrl=tEndpointUrl;
    }

    public ClientExampleRunner(ClientExample clientExample, boolean serverRequired) throws Exception {
        this.clientExample = clientExample;







        this.serverRequired = serverRequired;

        if (serverRequired) {
            /*exampleServer = new ExampleServer();
            exampleServer.startup().get();*/
        }
    }

    private OpcUaClient createClient() throws Exception {
        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            throw new Exception("unable to create security dir: " + securityTempDir);
        }

        File pkiDir = securityTempDir.resolve("pki").toFile();

        LoggerFactory.getLogger(getClass())
                .info("security dir: {}", securityTempDir.toAbsolutePath());
        LoggerFactory.getLogger(getClass())
                .info("security pki dir: {}", pkiDir.getAbsolutePath());

        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);

        trustListManager = new DefaultTrustListManager(pkiDir);

        DefaultClientCertificateValidator certificateValidator =
                new DefaultClientCertificateValidator(trustListManager);
        return create(
               this.tEndpointUrl,
                endpoints ->
                        endpoints.stream()
                                .filter(clientExample.endpointFilter())
                                .findFirst(),
                configBuilder ->
                        configBuilder
                                .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                                .setApplicationUri("urn:eclipse:milo:examples:client")
                                /*     .setKeyPair(loader.getClientKeyPair())
                                     .setCertificate(loader.getClientCertificate())
                                     .setCertificateChain(loader.getClientCertificateChain())
                                     .setCertificateValidator(certificateValidator)*/
                                //.setIdentityProvider(new UsernameProvider("Admin","123456"))
                                .setIdentityProvider(new AnonymousProvider())
                                .setRequestTimeout(uint(5000))
                                .build()
        );
    }
    public  OpcUaClient create(
            String endpointUrl,
            Function<List<EndpointDescription>, Optional<EndpointDescription>> selectEndpoint,
            Function<OpcUaClientConfigBuilder, OpcUaClientConfig> buildConfig
    ) throws UaException {
        try {
            List<EndpointDescription> endpoints =
                    DiscoveryClient.getEndpoints(endpointUrl).get();

            EndpointDescription endpointDescription = endpoints.get(0);
            EndpointDescription replaceEndpointDescription=new EndpointDescription(
                    tEndpointUrl,
                    endpointDescription.getServer(),
                    endpointDescription.getServerCertificate(),
                    endpointDescription.getSecurityMode(),
                    endpointDescription.getSecurityPolicyUri(),
                    endpointDescription.getUserIdentityTokens(),
                    endpointDescription.getTransportProfileUri(),
                    endpointDescription.getSecurityLevel()
            );
            OpcUaClientConfigBuilder builder = OpcUaClientConfig.builder()
                    .setEndpoint(replaceEndpointDescription);

            return OpcUaClient.create(buildConfig.apply(builder));
        } catch (InterruptedException | ExecutionException e) {
            if (!endpointUrl.endsWith("/discovery")) {
                StringBuilder discoveryUrl = new StringBuilder(endpointUrl);
                if (!endpointUrl.endsWith("/")) {
                    discoveryUrl.append("/");
                }
                discoveryUrl.append("discovery");

                return create(discoveryUrl.toString(), selectEndpoint, buildConfig);
            } else {
                throw UaException.extract(e)
                        .orElseGet(() -> new UaException(e));
            }
        }
    }
    public void run() {
        try {
            log.info("获取客户端。。。。。");
            OpcUaClient client = createClient();

            // For the sake of the examples we will create mutual trust between the client and
            // server so we can run them with security enabled by default.
            // If the client example is pointed at another server then the rejected certificate
            // will need to be moved from the security "pki/rejected" directory to the
            // "pki/trusted/certs" directory.

            // Make the example server trust the example client certificate by default.
        /*    client.getConfig().getCertificate().ifPresent(
                    certificate ->
                            exampleServer.getServer().getConfig().getTrustListManager().addTrustedCertificate(certificate)
            );*/

            // Make the example client trust the example server certificate by default.
          /*  exampleServer.getServer().getConfig().getCertificateManager().getCertificates().forEach(
                    certificate ->
                            trustListManager.addTrustedCertificate(certificate)
            );*/

            future.whenCompleteAsync((c, ex) -> {
                if (ex != null) {
                    logger.error("Error running example: {}", ex.getMessage(), ex);
                }

                try {
                    client.disconnect().get();
                 /*   if (serverRequired && exampleServer != null) {
                        exampleServer.shutdown().get();
                    }*/
                    Stack.releaseSharedResources();
                } catch (InterruptedException | ExecutionException e) {
                    logger.error("Error disconnecting: {}", e.getMessage(), e);
                }

                try {
                    Thread.sleep(1000);
                   // System.exit(0);
                   // logger.error("opc断开链接");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            try {
                clientExample.run(client, future);
                future.get(30, TimeUnit.SECONDS);
            } catch (Throwable t) {
                logger.error("Error running client example: {}", t.getMessage(), t);
                //future.completeExceptionally(t);
            }
        } catch (Throwable t) {
            logger.error("Error getting client: {}", t.getMessage(), t);

           /* future.completeExceptionally(t);

            try {
                Thread.sleep(1000);
                System.exit(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
           log.error("客户端断开链接》-------");
        }

        try {
            Thread.sleep(999_999_999);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
